<?php
/**
 * @license http://www.gnu.org/licenses/gpl.html GNU GPL, version 3 or later
 * @package FrontPress
 * @subpackage Route
 */

/**
 * Main entry point for frontends. Requires FP_Route objects to be used.
 *
 * @since   2010-07-27-r20
 * @version $Id: class.fp-router.php 39 2010-07-31 16:09:53Z Nightgunner5 $
 */
class FP_Router {
	private $urls = array();
	private $fourohfour;
	/**
	 * The currently active route. This is set inside {@link FP_Router::route()}
	 * and is primarily useful for plugins.
	 *
	 * @var FP_Route
	 */
	var $current_route;

	/**
	 * Returns the current request URI path, relative to
	 * the application URI and without the query string
	 *
	 * @return string|bool the current path or false if the path was not inside the
	 *                     application URI.
	 */
	function request_uri() {
		$url = parse_url( backpress_get_option( 'application_uri' ) );
		$subdir = untrailingslashit( $url['path'] );
		if ( preg_match( "@^$subdir(.*?)(\?.*)?$@", $_SERVER['REQUEST_URI'], $match ) )
			return urldecode( $match[1] );
		return false;
	}

	function request_method() {
		return isset( $_SERVER['REQUEST_METHOD'] ) ? $_SERVER['REQUEST_METHOD'] : 'GET';
	}

	/**
	 * Add a route to the router.
	 *
	 * @param string $re a regular expression that matches urls for the route
	 * @param callback $function a valid callback to use for matched urls
	 * @param string $method a HTTP method. Commonly 'get' or 'post'.
	 */
	function add( $re, $function, $method = 'get' ) {
		$this->urls[$method . ':' . $re] = $function;
	}

	/**
	 * Define the route that will be used if no other routes match.
	 *
	 * @param FP_Route $fourohfour a valid FP_Route instance with a not_found method
	 */
	function set_404( FP_Route $fourohfour ) {
		if ( !method_exists( $fourohfour, 'not_found' ) )
			trigger_error( 'FP_Router::set_404() requires a route with a not_found() method.', E_USER_ERROR );
		$this->fourohfour =& $fourohfour;
	}

	/**
	 * Send a 404 using the route set by {@link FP_Router::set_404()}.
	 *
	 * @access private
	 */
	function send_404() {
		$this->fourohfour->not_found();
	}

	function route() {
		if ( !isset( $this->fourohfour ) )
			trigger_error( 'FP_Router::set_404() must be called before routing.', E_USER_ERROR );

		$request_uri = $this->request_uri();
		$request_method = strtolower( $this->request_method() );
		uksort( $this->urls, 'frontpress_length_sort' );
		$this->urls = array_reverse( $this->urls, true );
		foreach( $this->urls as $re => $func ) {
			foreach ( array( 'get', 'post', 'head', 'put', 'delete' ) as $http_method ) {
				if ( substr( $re, 0, strlen( $http_method ) + 1 ) == $http_method . ':' ) {

					if ( $http_method != $request_method ) continue;
					$re = substr( $re, strlen( $http_method . ':' ));
					break;
				}
			}
			if ( preg_match("@^$re$@", $request_uri, $matches ) ) {
				if ( is_array( $func ) ) {
					list( $class, $method ) = $func;
					if ( is_string( $class ) )
						$route = new $class;
					else
						$route =& $class;
					$route->last_method_called = $method;
					$route->router =& $this;
					$this->current_route =& $route;
					$route->before_request();
					$route->request_running = true;
					// make sure after_request() is called even if we $this->exit_() in the request
					register_shutdown_function( array( &$route, 'after_request') );
					call_user_func_array( array( $route, $method ), array_slice( $matches, 1 ) );
					$route->after_request();
					do_action( 'after_request', $class, $method );
					$route->request_running = false;
				} else {
					call_user_func_array( $func, array_slice( $matches, 1 ) );
				}
				return;
			}
		}

		return $this->fourohfour->not_found();
	}
}
